/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014年9月24日
 * V4.0
 */
package com.jphenix.service.db.common.instancea;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jphenix.driver.serialno.FSN;
import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.lang.SString;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.docs.Running;
import com.jphenix.standard.serialno.ISN;

/**
 * 数据操作服务（常驻内存）
 * 
 * 注意：不支持严格要求大小写的数据库（吃饱了撑的）
 * 
 * 2018-12-24 去掉了获取表状态抛异常。操作表状态时，可以通过别名识别真实数据源
 * 2019-02-28 增加了重置缓存功能，比如已经将某表的字段信息驻留内存后，并不会因为表状态发生变化重新加载（能做到，但表结构发生和那个变化属于特例，没必要在正常使用时检查）
 * 2019-06-26 修改了序列号生成器的构造方法
 * 2020-09-05 增加了将调用者需要保存的表状态值放入该服务中保存，简化了调用者使用缓存查询方法
 * 2020-09-09 dataChange方法中传入的表名支持用逗号分隔多个表名，同时检测多个表其中是否有发生数据变化的
 * 
 * @author 马宝刚
 * 2014年9月24日
 */
@ClassInfo({"2020-09-09 20:00","数据操作服务（常驻内存）"})
@BeanInfo({"dbquerysvc"})
@Running({"90"})
public class DBQuerySvc extends ABase {

    /*
     * 序列号生成器
     */
    private ISN sn = null; 
    
    /*
     * 表字段名序列
     * 
     * key: 数据源主键
     * value: Map-> key:表名（小写）
     *                       value: 字段名序列（小写）
     */
    private Map<String,Map<String,List<String>>> tableFieldMap = new HashMap<>();
    
    /*
     * 主键字段名容器
     * 
     * key: 数据源主键
     * value: Map -> key:表名 value:表中主键字段名
     */
    private Map<String,Map<String,String>> pkFieldMap = new HashMap<>();
    
    /*
     * 表状态容器
     * 
     * key: 数据源主键
     * value: Map -> key:表名  value:表状态
     */
    private Map<String,Map<String,String>> tableStateMap = new HashMap<>();
    
    /**
     * 保存在客户端的表状态（在服务端也保存一份）直接在服务端判断表是否
     * 
     * 注意：每个类获取相同数据源，相同表名的表状态的类实例数量不能超过1个
     *      （有的错误服务并没有驻留内存，导致每次获取表状态都是新实例，服务端都得为新实例保存表状态值，导致服务端
     *        数据保存过多，导致内存溢出）
     * 
     * key: 调用者类名_数据源主键_表名 value：表状态值
     */
    private Map<String,String> clientTableStateMap = new HashMap<>();
    
    /*
     * 别名，源数据源主键对照容器
     */
    protected Map<String,String> sourceKeyMap = new HashMap<>();
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public DBQuerySvc() {
        super();
    }
    
    /**
     * 获取序列号生成器
     * @return 序列号生成器
     * 2014年9月25日
     * @author 马宝刚
     */
    private ISN getSn() {
        if(sn==null) {
        	sn = FSN.newInstance("TS",20);
        }
        return sn;
    }
    
    /**
     * 获取指定表的表状态
     * 
     * 表状态：随机字符串，每次表中数据发生变化（即操作新增、更新、删除等动作，刷新该状态值）
     * 
     * 通常用于数据数量固定，经常读取数据。将这些数据常驻内存，每次获取前，先获取表状态值
     * 与常驻内存中的表状态做比较，如果不一致，说明该表中的数据做过更新，然后重新将数据加载到内存中
     * 
     * @param tableName 表名
     * @param dsName 数据源主键
     * @return 对应的表状态字符串
     * 2014年9月25日
     * @author 马宝刚
     */
    public String getTableState(String tableName,String dsName) {
        //获取指定数据源信息容器
    	if(tableName==null) {
    		return "";
    	}
    	if(sourceKeyMap.containsKey(dsName)) {
    		//由别名转换为真实数据库配置信息
    		dsName = str(sourceKeyMap.get(dsName));
    	}
    	tableName = tableName.toLowerCase();
        Map<String,String> tMap = tableStateMap.get(dsName);
        if(tMap==null) {
            tMap = new HashMap<String,String>();
            tableStateMap.put(dsName,tMap);
        }
      //表状态
        String tableState = SString.valueOf(tMap.get(tableName)); 
        if(tableState.length()<1) {
            tableState = getSn().getSID();
            tMap.put(tableName,tableState);
        }
       return tableState;
    }
    
    
    /**
     * 判断调用者获取到指定表的数据状态是否发生了变化（或者是首次获取数据）
     * @param caller     调用者类实例
     * @param dsName     数据源主键
     * @param tableName  表名
     * @return           数据库中指定表的数据是否发生了变化（与类实例中保存的数据不一致）
     * 2020年9月4日
     * @author MBG
     */
    public boolean dataChange(Object caller,String dsName,String tableName,boolean force) {
    	if(caller==null || dsName==null 
    			|| dsName.length()<1 || tableName==null 
    			|| tableName.length()<1) {
    		//非法传入值，不做任何处理
    		return false;
    	}
    	String   key;                                 //获取表状态值的主键
    	String   newState;                            //当前表状态值
    	String clientState;                           //从客户端容器中获取表状态值
    	tableName          = tableName.toLowerCase(); //转换成小写
    	boolean  res       = false;                   //返回值
    	String[] tns       = tableName.split(",");    //分隔出多个表
    	for(int i=0;i<tns.length;i++) {
    		key      = caller.getClass().getName()+"_"+dsName+"_"+tns[i];
    		newState = getTableState(tns[i],dsName);
        	if(force) {
        		//强制更新表状态
        		clientTableStateMap.put(key,newState);
        		res = true;
        		continue;
        	}
        	clientState = clientTableStateMap.get(key);
        	if(clientState==null || !newState.equals(clientState)) {
        		//返回客户端的表状态值为空，或者当前表状态值已经发生变化
        		clientTableStateMap.put(key,newState);
        		res = true;
        		continue;
        	}
    	}
    	return res;
    }
    
    
    
    /**
     * 刷新指定表的表状态值
     * @param tableName 表名
     * @param dsName 数据源主键
     * @return 新的表状态值
     * 2014年9月25日
     * @author 马宝刚
     */
    public String refreshTableState(String tableName,String dsName) {
    	if(tableName==null) {
    		return "";
    	}
    	if(sourceKeyMap.containsKey(dsName)) {
    		//由别名转换为真实数据库配置信息
    		dsName = str(sourceKeyMap.get(dsName));
    	}
    	tableName = tableName.toLowerCase();
        //获取指定数据源信息容器
        Map<String,String> tMap = tableStateMap.get(dsName);
        if(tMap==null) {
            tMap = new HashMap<String,String>();
            tableStateMap.put(dsName,tMap);
        }
        //新的表状态
        String tableState = getSn().getSID();
        tMap.put(tableName,tableState);
        return tableState;
    }
    
    /**
     * 设置指定表的主键字段名
     * @param pkFieldName 主键字段名
     * @param tableName 表名
     * @param dsName 数据源主键
     * 2014年9月24日
     * @author 马宝刚
     */
    public void setPkFieldName(String pkFieldName,String tableName,String dsName) {
        //获取指定数据源信息容器
        Map<String,String> tMap = pkFieldMap.get(dsName);
        if(tMap==null) {
            tMap = new HashMap<String,String>();
            pkFieldMap.put(dsName,tMap);
        }
        tMap.put(tableName,pkFieldName);
    }
    
    /**
     * 获取指定表的主键字段名
     * @param tableName 表名
     * @param dsName 数据源主键
     * @return 主键字段名
     * 2014年9月24日
     * @author 马宝刚
     */
    public String getPKFieldName(String tableName,String dsName) {
        //获取指定数据源信息容器
        Map<String,String> tMap = pkFieldMap.get(dsName);
        if(tMap==null) {
            tMap = new HashMap<String,String>();
            pkFieldMap.put(dsName,tMap);
        }
       return SString.valueOf(tMap.get(tableName));
    }

    /**
     * 获取指定数据源，指定表名的所有字段序列
     * @param tableName 表名
     * @param dsName 数据源主键
     * @return 字段序列
     * 2014年9月24日
     * @author 马宝刚
     */
    public List<String> getFieldList(String tableName,String dsName){
        //获取指定数据源信息容器
        Map<String,List<String>> tMap = tableFieldMap.get(dsName);
        if(tMap==null) {
            tMap = new HashMap<String,List<String>>();
            tableFieldMap.put(dsName,tMap);
        }
        List<String> fieldList = tMap.get(tableName);
        if(fieldList==null) {
            fieldList = new ArrayList<String>();
            tMap.put(tableName,fieldList);
        }
        return fieldList;
    }
    
    /**
     * 重置缓存
     * 2019年2月28日
     * @author MBG
     */
    public void clear() {
    	tableFieldMap.clear();
    	pkFieldMap.clear();
    	tableStateMap.clear();
    	sourceKeyMap.clear();
    }
}
